﻿using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using Box2DX.Common;
using Box2DX.Dynamics;

namespace Expression.Samples.Physics
{
	public class Entity
	{
		private Body body;
		private FrameworkElement element;
		private FrameworkElement shape;
		private PhysicsContext context;
		private BodyParameters bodyParameters;
		private Point worldOffset;

		IBodyMover bodyMover;

		public Entity(FrameworkElement element) {

			if (Entity.GetElementEntity(element) != null)
				throw new ArgumentException("Element already has an entity defined.");

			Entity.SetElementEntity(element, this);

			this.element = element;
		}

		public void Initialize(PhysicsContext context, BodyParameters bodyParameters) {

			this.context = context;
			this.bodyParameters = bodyParameters;

			IShapeProvider shapeProvider = element as IShapeProvider;
			this.shape = element;
			if (shapeProvider != null)
				this.shape = shapeProvider.BodyShape;

			if (this.shape != null) {

				this.bodyMover = this.element as IBodyMover;
				if (this.bodyMover == null)
					this.bodyMover = new BodyMover(this.element);

				Transform worldTransform = (Transform)this.shape.TransformToVisual(this.context.Host);
				MatrixTransform worldMatrix = (MatrixTransform)worldTransform;


				this.worldOffset = new Point(worldMatrix.Matrix.OffsetX, worldMatrix.Matrix.OffsetY);


				bodyParameters.UserData = this;

				this.body = BodyHelper.GetBodyForElement(this.Simulator, this, bodyParameters);
			}
		}


		public Simulator Simulator
		{
			get { return this.context.Simulator; }
		}

		public Body Body
		{
			get { return this.body; }
		}

		public object UserData { get; set; }

		public FrameworkElement Element
		{
			get { return this.element; }
		}

		public FrameworkElement Shape {
			get { return this.shape; }
		}
		

		public Vector2 BodySize
		{
			get { return this.Simulator.SimulatorContext.ScreenSizeToWorldSize(this.ElementSize); }
		}

		public Vector2 BodyPosition
		{
			get { return this.Body.Position; }
		}

		public double BodyAngle
		{
			get { return this.Body.Angle; }
		}

		public Size ElementSize {
			get {
				return this.Shape.RenderSize;
			}
		}

		public Point ElementPosition {
			get {
				return this.bodyMover.ElementPosition.Add(this.worldOffset);
			}
			set {
				value = value.Sub(this.worldOffset);
				this.bodyMover.ElementPosition = value;
			}
		}

		public Point ElementCenter {
			get { return this.ElementPosition.Add(this.ElementSize.Mul(0.5)); }
		}

		public double ElementAngle {
			get {
				return this.bodyMover.ElementAngle / 180 * System.Math.PI;
			}
			set {
				if (this.bodyParameters.CanRotate) {
					this.bodyMover.ElementAngle = value / System.Math.PI * 180;
				}
			}
		}

		public EventHandler<CollideEventArgs> Collide;

		public void OnCollide(ContactPoint point) {
			if (this.Collide != null) {
				Entity a = (Entity)point.Shape1.UserData;
				Entity b = (Entity)point.Shape2.UserData;

				if (a == this)
					this.Collide(this, new CollideEventArgs(point, a, b));
				else
					this.Collide(this, new CollideEventArgs(point, b, a));
			}
		}

		public EventHandler BoundaryViolation;
		public void OnBoundaryViolation() {
			if (this.BoundaryViolation != null)
				this.BoundaryViolation(this, EventArgs.Empty);
		}

		public static readonly DependencyProperty ElementEntityProperty = DependencyProperty.RegisterAttached("ElementEntity", typeof(Entity), typeof(Entity), new PropertyMetadata(null));

		public static Entity GetElementEntity(FrameworkElement element) {
			return (Entity)element.GetValue(Entity.ElementEntityProperty);
		}

		public static void SetElementEntity(FrameworkElement element, Entity entity) {
			element.SetValue(Entity.ElementEntityProperty, entity);
		}
		
	}

	public class CollideEventArgs: EventArgs {
		public CollideEventArgs(ContactPoint contactPoint, Entity self, Entity other) {
			this.ContactPoint = contactPoint;

			this.Self = self;
			this.Other = other;
		}

		public ContactPoint ContactPoint { get; private set; }

		public Entity Self { get; private set; }
		public Entity Other { get; private set; }
	}
}
